#include "chunkManager.hpp"
#include "coroutine/coroutine.cpp"

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

namespace minecraft {
	class RenderHelper : public Coroutine {
		public:
			bool done;
			Ogre::ManualObject* rChunk;
			
			RenderHelper(int rx, int ry, int rz) {done=false; cx = rx; cy = ry; cz = rz;}
            ~RenderHelper() {}
			void Routine() {
				unsigned long long t1;
				unsigned long long t2;
			
			
				t1 = rdtsc();
			
			   //get the chunk and create mesh for it
				chunk* c = sChunkManager::Instance()->getChunk(this->cx, this->cy, this->cz);
				Ogre::ManualObject* rChunk = new Ogre::ManualObject("chunk_" + c->getId());
				
				if (c->isEmpty()) {
					//return if empty
					this->rChunk = rChunk;
					this->done = true;
					while(true) {
						Detach();
						
						//std::cout << "Return in loop ... ops" << "\n";
					}
				}
				
				//iterate chunk types
				b_tlist types = c->getTypes();
				for (b_tlist::const_iterator it = types.begin(); it != types.end(); ++it) {
					//current block we are looking at
					boost::shared_ptr<block> type = sBlockManager::Instance()->create(*it);
					rChunk->begin(type->getTextureName());
					
					//helper variable for vertex optimization
					unsigned int vert = 0;
					
					for (unsigned int x = 0; x < P_CHUNKSIZE; ++x) {
						for (unsigned int y = 0; y < P_CHUNKSIZE; ++y) {
							for (unsigned int z = 0; z < P_CHUNKSIZE; ++z) {
								//check if the block requested has the current material
								b_type block = c->get(x,y,z);
								if (block == (*it)) {							
									if(block == 0) continue;
									
									//quicksave global coordinate
									int gx = (this->cx*P_CHUNKSIZE)+x;
									int gy = (this->cy*P_CHUNKSIZE)+y;
									int gz = (this->cz*P_CHUNKSIZE)+z;
									
									//x position -1 / +1
									if(x > 0){
									  block = c->get(x-1,y,z);
									} else{
									  block = sChunkManager::Instance()->getBlock(gx-1,gy,gz);
									}
									
									if(block == 0){
										rChunk->position(x, y, z+1);   rChunk->normal(-1, 0, 0); rChunk->textureCoord(0, 1);
										rChunk->position(x, y+1, z+1); rChunk->normal(-1, 0, 0); rChunk->textureCoord(1, 1);
										rChunk->position(x, y+1, z);   rChunk->normal(-1, 0, 0); rChunk->textureCoord(1, 0);
										rChunk->position(x, y, z);     rChunk->normal(-1, 0, 0); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
									
									if(x+1 < P_CHUNKSIZE){
										block = c->get(x+1,y,z);
									} else{
										block = sChunkManager::Instance()->getBlock(gx+1,gy,gz);
									}
									if(block == 0){
										rChunk->position(x+1, y, z);     rChunk->normal(1, 0, 0); rChunk->textureCoord(0, 1);
										rChunk->position(x+1, y+1, z);   rChunk->normal(1, 0, 0); rChunk->textureCoord(1, 1);
										rChunk->position(x+1, y+1, z+1); rChunk->normal(1, 0, 0); rChunk->textureCoord(1, 0);
										rChunk->position(x+1, y, z+1);   rChunk->normal(1, 0, 0); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
									
									//y position -1 / +1
									if(y > 0){
									  block = c->get(x,y-1,z);
									} else{
									  block = sChunkManager::Instance()->getBlock(gx,gy-1,gz);
									}
									if(block == 0){
										rChunk->position(x, y, z);     rChunk->normal(0, -1, 0); rChunk->textureCoord(0, 1);
										rChunk->position(x+1, y, z);   rChunk->normal(0, -1, 0); rChunk->textureCoord(1, 1);
										rChunk->position(x+1, y, z+1); rChunk->normal(0, -1, 0); rChunk->textureCoord(1, 0);
										rChunk->position(x, y, z+1);   rChunk->normal(0, -1, 0); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
									
									if(y+1 < P_CHUNKSIZE){
									  block = c->get(x,y+1,z);
									} else{
									  block = sChunkManager::Instance()->getBlock(gx,gy+1,gz);
									}
									if(block == 0){
										rChunk->position(x, y+1, z+1);   rChunk->normal(0, 1, 0); rChunk->textureCoord(0, 1);
										rChunk->position(x+1, y+1, z+1); rChunk->normal(0, 1, 0); rChunk->textureCoord(1, 1);
										rChunk->position(x+1, y+1, z);   rChunk->normal(0, 1, 0); rChunk->textureCoord(1, 0);
										rChunk->position(x, y+1, z);     rChunk->normal(0, 1, 0); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
									
									//z position -1 / +1
									if(z > 0){
									  block = c->get(x,y,z-1);
									} else{
									  block = sChunkManager::Instance()->getBlock(gx,gy,gz-1);
									}
									if(block == 0){
										rChunk->position(x, y+1, z);   rChunk->normal(0, 0, -1); rChunk->textureCoord(0, 1);
										rChunk->position(x+1, y+1, z); rChunk->normal(0, 0, -1); rChunk->textureCoord(1, 1);
										rChunk->position(x+1, y, z);   rChunk->normal(0, 0, -1); rChunk->textureCoord(1, 0);
										rChunk->position(x, y, z);     rChunk->normal(0, 0, -1); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
									
									if(z+1 < P_CHUNKSIZE){
									  block = c->get(x,y,z+1);
									} else{
									  block = sChunkManager::Instance()->getBlock(gx,gy,gz+1);
									}
									if(block == 0){
										rChunk->position(x, y, z+1);     rChunk->normal(0, 0, 1); rChunk->textureCoord(0, 1);
										rChunk->position(x+1, y, z+1);   rChunk->normal(0, 0, 1); rChunk->textureCoord(1, 1);
										rChunk->position(x+1, y+1, z+1); rChunk->normal(0, 0, 1); rChunk->textureCoord(1, 0);
										rChunk->position(x, y+1, z+1);   rChunk->normal(0, 0, 1); rChunk->textureCoord(0, 0);
										
										rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
										vert += 4;
									}
								}
							}
							
							//check timing and detach if timing is too long
							
							t2 = rdtsc();
							
							if ((t2-t1) / 1000000 > 15) {
								Detach();
								t1 = rdtsc();
							}
							
						}
					}
					
					//end current material
					rChunk->end();
				}
				
				this->rChunk = rChunk;
				this->done = true;
				while(true) {
					Detach();
					
					//std::cout << "Came back in loop ... ops\n";
				}
			}
		private:
			int cx, cy, cz;
	};
}